package gb28181

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	config "lalmax/conf"
	"net/http"
	"strconv"
	"strings"
	"sync/atomic"
	"time"

	"github.com/ghettovoice/gosip/sip"
	"github.com/q191201771/lal/pkg/base"
	"github.com/q191201771/naza/pkg/nazalog"
)

type Channel struct {
	device    *Device      // 所属设备
	status    atomic.Int32 // 通道状态,0:空闲,1:正在invite,2:正在播放
	GpsTime   time.Time    // gps时间
	Longitude string       // 经度
	Latitude  string       // 纬度
	ChannelInfo
}

// Channel 通道
type ChannelInfo struct {
	DeviceID     string // 通道ID
	ParentID     string
	Name         string
	Manufacturer string
	Model        string
	Owner        string
	CivilCode    string
	Address      string
	Port         int
	Parental     int
	SafetyWay    int
	RegisterWay  int
	Secrecy      int
	Status       ChannelStatus
}

type ChannelStatus string

const (
	ChannelOnStatus  = "ON"
	ChannelOffStatus = "OFF"
)

func (channel *Channel) TryAutoInvite(opt *InviteOptions, conf config.GB28181Config) {
	if channel.CanInvite() {
		go channel.Invite(opt, conf)
	}
}

func (channel *Channel) CanInvite() bool {
	if channel.status.Load() != 0 || len(channel.DeviceID) != 20 || channel.Status == ChannelOffStatus {
		nazalog.Info("return false, channel.status.Load():", channel.status.Load(), " channel.DeviceID:", len(channel.DeviceID), " channel.Status:", channel.Status)
		return false
	}

	// 11～13位是设备类型编码
	typeID := channel.DeviceID[10:13]
	tokens := strings.Split("132", ",")
	for _, tok := range tokens {
		if first, second, ok := strings.Cut(tok, "-"); ok {
			if typeID >= first && typeID <= second {
				return true
			}
		} else {
			if typeID == first {
				return true
			}
		}
	}

	nazalog.Info("return false")

	return false
}

// Invite 发送Invite报文 invites a channel to play
// 注意里面的锁保证不同时发送invite报文，该锁由channel持有
/***
f字段： f = v/编码格式/分辨率/帧率/码率类型/码率大小a/编码格式/码率大小/采样率
各项具体含义：
    v：后续参数为视频的参数；各参数间以 “/”分割；
编码格式：十进制整数字符串表示
1 –MPEG-4 2 –H.264 3 – SVAC 4 –3GP
    分辨率：十进制整数字符串表示
1 – QCIF 2 – CIF 3 – 4CIF 4 – D1 5 –720P 6 –1080P/I
帧率：十进制整数字符串表示 0～99
码率类型：十进制整数字符串表示
1 – 固定码率（CBR）     2 – 可变码率（VBR）
码率大小：十进制整数字符串表示 0～100000（如 1表示1kbps）
    a：后续参数为音频的参数；各参数间以 “/”分割；
编码格式：十进制整数字符串表示
1 – G.711    2 – G.723.1     3 – G.729      4 – G.722.1
码率大小：十进制整数字符串
音频编码码率： 1 — 5.3 kbps （注：G.723.1中使用）
   2 — 6.3 kbps （注：G.723.1中使用）
   3 — 8 kbps （注：G.729中使用）
   4 — 16 kbps （注：G.722.1中使用）
   5 — 24 kbps （注：G.722.1中使用）
   6 — 32 kbps （注：G.722.1中使用）
   7 — 48 kbps （注：G.722.1中使用）
   8 — 64 kbps（注：G.711中使用）
采样率：十进制整数字符串表示
	1 — 8 kHz（注：G.711/ G.723.1/ G.729中使用）
	2—14 kHz（注：G.722.1中使用）
	3—16 kHz（注：G.722.1中使用）
	4—32 kHz（注：G.722.1中使用）
	注1：字符串说明
本节中使用的“十进制整数字符串”的含义为“0”～“4294967296” 之间的十进制数字字符串。
注2：参数分割标识
各参数间以“/”分割，参数间的分割符“/”不能省略；
若两个分割符 “/”间的某参数为空时（即两个分割符 “/”直接将相连时）表示无该参数值；
注3：f字段说明
使用f字段时，应保证视频和音频参数的结构完整性，即在任何时候，f字段的结构都应是完整的结构：
f = v/编码格式/分辨率/帧率/码率类型/码率大小a/编码格式/码率大小/采样率
若只有视频时，音频中的各参数项可以不填写，但应保持 “a///”的结构:
f = v/编码格式/分辨率/帧率/码率类型/码率大小a///
若只有音频时也类似处理，视频中的各参数项可以不填写，但应保持 “v/”的结构：
f = v/a/编码格式/码率大小/采样率
f字段中视、音频参数段之间不需空格分割。
可使用f字段中的分辨率参数标识同一设备不同分辨率的码流。
*/

func (channel *Channel) Invite(opt *InviteOptions, conf config.GB28181Config) (code int, err error) {
	if opt.IsLive() {
		if !channel.status.CompareAndSwap(0, 1) {
			return 304, nil
		}
		defer func() {
			if err != nil {
				channel.status.Store(0)
				time.AfterFunc(time.Second*5, func() {
					channel.Invite(opt, conf)
				})
			} else {
				channel.status.Store(2)
			}
		}()
	}
	d := channel.device
	s := "Play"
	opt.CreateSSRC(conf.Serial)

	protocol := ""
	networkType := conf.SipNetwork

	nazalog.Info("networkType:", networkType)

	// 获取lal的端口
	opt.MediaPort, _ = d.DoLal(channel.DeviceID, networkType)
	if networkType == "tcp" {
		protocol = "TCP/"
	}
	if err != nil {
		return http.StatusInternalServerError, err
	}

	sdpInfo := []string{
		"v=0",
		fmt.Sprintf("o=%s 0 0 IN IP4 %s", channel.DeviceID, d.mediaIP),
		"s=" + s,
		"c=IN IP4 " + d.mediaIP,
		opt.String(),
		fmt.Sprintf("m=video %d %sRTP/AVP 96", opt.MediaPort, protocol),
		"a=recvonly",
		"a=rtpmap:96 PS/90000",
		"y=" + opt.ssrc,
	}

	if networkType == "tcp" {
		sdpInfo = append(sdpInfo, "a=setup:passive", "a=connection:new")
	}

	invite := channel.CreateRequst(sip.INVITE, conf)
	contentType := sip.ContentType("application/sdp")
	invite.AppendHeader(&contentType)

	contentLength := sip.ContentLength(len(sdpInfo))
	invite.AppendHeader(&contentLength)

	invite.SetBody(strings.Join(sdpInfo, "\r\n")+"\r\n", true)

	subject := sip.GenericHeader{
		HeaderName: "Subject", Contents: fmt.Sprintf("%s:%s,%s:0", channel.DeviceID, opt.ssrc, ""),
	}
	invite.AppendHeader(&subject)
	inviteRes, err := d.SipRequestForResponse(invite)
	if err != nil {
		nazalog.Error("invite failed, err:", err, " invite msg:", invite.String())
		return http.StatusInternalServerError, err
	}
	code = int(inviteRes.StatusCode())
	if code == http.StatusOK {
		ds := strings.Split(inviteRes.Body(), "\r\n")
		for _, l := range ds {
			if ls := strings.Split(l, "="); len(ls) > 1 {
				if ls[0] == "y" && len(ls[1]) > 0 {
					if _ssrc, err := strconv.ParseInt(ls[1], 10, 0); err == nil {
						opt.SSRC = uint32(_ssrc)
					} else {
						nazalog.Error("parse invite response y failed, err:", err)
					}
				}
				if ls[0] == "m" && len(ls[1]) > 0 {
					netinfo := strings.Split(ls[1], " ")
					if strings.ToUpper(netinfo[2]) == "TCP/RTP/AVP" {
						nazalog.Info("Device support tcp")
					} else {
						nazalog.Info("Device not support tcp")
						networkType = "udp"
					}
				}
			}
		}

		err = sipsvr.Send(sip.NewAckRequest("", invite, inviteRes, "", nil))
	}
	return
}

func (channel *Channel) CreateRequst(Method sip.RequestMethod, conf config.GB28181Config) (req sip.Request) {
	d := channel.device
	d.sn++

	callId := sip.CallID(RandNumString(10))
	userAgent := sip.UserAgentHeader("LALMax")
	maxForwards := sip.MaxForwards(70) //增加max-forwards为默认值 70
	cseq := sip.CSeq{
		SeqNo:      uint32(d.sn),
		MethodName: Method,
	}
	port := sip.Port(conf.SipPort)
	serverAddr := sip.Address{
		Uri: &sip.SipUri{
			FUser: sip.String{Str: conf.Serial},
			FHost: d.sipIP,
			FPort: &port,
		},
		Params: sip.NewParams().Add("tag", sip.String{Str: RandNumString(9)}),
	}
	//非同一域的目标地址需要使用@host
	host := conf.Realm
	if channel.DeviceID[0:9] != host {
		if channel.Port != 0 {
			deviceIp := d.NetAddr
			deviceIp = deviceIp[0:strings.LastIndex(deviceIp, ":")]
			host = fmt.Sprintf("%s:%d", deviceIp, channel.Port)
		} else {
			host = d.NetAddr
		}
	}

	channelAddr := sip.Address{
		Uri: &sip.SipUri{FUser: sip.String{Str: channel.DeviceID}, FHost: host},
	}
	req = sip.NewRequest(
		"",
		Method,
		channelAddr.Uri,
		"SIP/2.0",
		[]sip.Header{
			serverAddr.AsFromHeader(),
			channelAddr.AsToHeader(),
			&callId,
			&userAgent,
			&cseq,
			&maxForwards,
			serverAddr.AsContactHeader(),
		},
		"",
		nil,
	)

	req.SetTransport(conf.SipNetwork)
	req.SetDestination(d.NetAddr)
	return req
}

func (d *Device) DoLal(deviceId, networkType string) (uint16, error) {
	request := &base.ApiCtrlStartRtpPubReq{
		StreamName: deviceId,
	}

	if networkType == "tcp" {
		request.IsTcpFlag = 1
	}

	data, _ := json.Marshal(request)

	req, err := http.NewRequest("POST", "http://127.0.0.1:8083/api/ctrl/start_rtp_pub", bytes.NewReader(data))
	if err != nil {
		return 0, err
	}

	req.Header.Set("Content-Type", "application/json")

	cli := &http.Client{
		Transport: http.DefaultTransport,
		Timeout:   time.Duration(5) * time.Second,
	}

	resp, err := cli.Do(req)
	if err != nil {
		return 0, err
	}

	if resp.StatusCode != 200 {
		return 0, fmt.Errorf("Response is not 200: %v", resp.Status)
	}

	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return 0, err
	}

	response := &base.ApiCtrlStartRtpPubResp{}
	err = json.Unmarshal(body, &response)
	if err != nil {
		return 0, err
	}

	nazalog.Info("start_rtp_pub response:", response)

	return uint16(response.Data.Port), nil
}
